package com.atguigu.tingshu.order.service.impl;

import java.util.Date;

import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.rabbit.constant.MqConst;
import com.atguigu.tingshu.common.rabbit.service.RabbitService;
import com.atguigu.tingshu.model.order.OrderDerate;
import com.atguigu.tingshu.model.order.OrderDetail;
import com.atguigu.tingshu.order.adapter.PayWay;
import com.atguigu.tingshu.order.mapper.OrderDerateMapper;
import com.atguigu.tingshu.order.mapper.OrderDetailMapper;
import com.atguigu.tingshu.vo.user.UserPaidRecordVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.google.common.collect.Maps;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.album.client.AlbumInfoFeignClient;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.common.util.MD5;
import com.atguigu.tingshu.model.album.AlbumInfo;
import com.atguigu.tingshu.model.album.TrackInfo;
import com.atguigu.tingshu.model.user.VipServiceConfig;
import com.atguigu.tingshu.order.helper.SignHelper;
import com.atguigu.tingshu.user.client.UserInfoFeignClient;
import com.atguigu.tingshu.vo.order.OrderDerateVo;
import com.atguigu.tingshu.vo.order.OrderDetailVo;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import com.google.common.collect.Lists;

import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.model.order.OrderInfo;
import com.atguigu.tingshu.order.mapper.OrderInfoMapper;
import com.atguigu.tingshu.order.service.OrderInfoService;
import com.atguigu.tingshu.vo.order.OrderInfoVo;
import com.atguigu.tingshu.vo.order.TradeVo;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.simpleframework.xml.Order;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo> implements OrderInfoService {

    @Autowired
    private OrderInfoMapper orderInfoMapper;

    @Autowired
    private UserInfoFeignClient userInfoFeignClient;

    @Autowired
    private AlbumInfoFeignClient albumInfoFeignClient;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private OrderDetailMapper orderDetailMapper;

    @Autowired
    private OrderDerateMapper orderDerateMapper;

    @Autowired
    private List<PayWay> payWayService;

    @Autowired
    private RabbitService rabbitService;

    /**
     * 交易，生成结算页
     * @param tradeVo
     * @return
     */
    @Override
    public OrderInfoVo trade(TradeVo tradeVo) {
        Long userId = AuthContextHolder.getUserId();

        // 1.创建OrderInfoVo对象
        OrderInfoVo orderInfoVo = null;

        String itemType = tradeVo.getItemType();
        switch (itemType) {
            case "1001":  // 专辑
                // 处理付款项类型是专辑的结算页
                orderInfoVo = dealItemTypeAlbum(tradeVo, userId);
                break;
            case "1002":  // 声音
                // 处理付款项类型是声音的结算页
                orderInfoVo = dealItemTypeTrack(tradeVo, userId);
                break;

            case "1003":  // vip套餐
                // 处理付款项类型是vip套餐的结算页
                orderInfoVo = dealItemTypeVip(tradeVo, userId);


        }
        /**下面代码的含义如下：
         * 检查订单商品列表是否为空。
         * 为订单信息生成签名，确保数据完整性。
         * 使用 Redis 存储防重提交标识，避免同一订单被多次提交。
         *
         *生成了一个时间戳和一个方法签名。以及向redis中set设置了一个 订单重复提交的key存到了redis中，用于后续提交订单中防止重复提交订单。
         */
//        获取商品列表：
//从 orderInfoVo 中提取商品详情列表 OrderDetailVo，并将其转换为一个标准的 List。
        List<OrderDetailVo> productList = orderInfoVo.getOrderDetailVoList().stream().collect(Collectors.toList());
            //判断商品列表是否为空：如果为空，直接返回 orderInfoVo 对象。表示没有需要处理的商品
        if (CollectionUtils.isEmpty(productList)) {
            return orderInfoVo;
        }

        // 签名设置 //为 orderInfoVo 设置当前时间戳，用于后续的签名验证。
        //将 orderInfoVo 转换为 JSON 字符串，再解析为 Map 类型。
        //调用 SignHelper.getSign 方法生成签名值，并将签名值设置到 orderInfoVo 中。
        //签名的作用是确保订单数据在传输过程中未被篡改。
        orderInfoVo.setTimestamp(System.currentTimeMillis());//设置时间戳
        String sign = SignHelper.getSign(JSONObject.parseObject(JSONObject.toJSONString(orderInfoVo),
                Map.class));
        orderInfoVo.setSign(sign);
/**生成防重提交的标识：
 * 将商品列表 productList 转换为字符串，并通过 MD5 加密生成一个具有代表性的令牌 representativeToken。
 * 结合用户 ID 和令牌生成唯一的防重提交键 orderRepeatSubmitKey。
 */
        String representativeToken = MD5.encrypt(new String(productList + ""));
        String orderRepeatSubmitKey = userId + ":" + representativeToken;  // 有代表性（代表买的商品内容）
        /**
         * 将防重提交键存储到 Redis 中，值为 "1"。
         * 这一步是为了防止同一个订单被重复提交。如果后续再次接收到相同的订单请求，可以通过检查 Redis
         * 中是否存在该键来判断是否重复提交。
         */
        redisTemplate.opsForValue().set(orderRepeatSubmitKey, "1");
        return orderInfoVo;//最终返回处理后的 orderInfoVo。

        // orderInfoVo--- originalAmount:100
        // Map（originalAmount:100）
        // 100 | "atguigu123"--->md5--->"123456789"
    }

    @Override
    public Map<String, Object> submitOrder(OrderInfoVo orderInfoVo) {

        // MD5-->不可逆
        // "abcd"---md5--->"123abcdef"
        // "abc"----md5--->"234abcdef"

        // orderInfoVo(originalAmount:200)--->Map(originalAmount:200)---签名---200 | "atguigu123"---md5---"123456789xb"


        // 本质：将提交给我们的数据存储到对应的数据库中表下。不会重复提交到数据库


        // think--->1.将前端提交的数据 保存到tingshu_order库下的三种表
        // （order_derate 订单减免表，订单详情表order_detail,订单基本信息表 order_info）
        // 2.生成订单编号 3.支付方法的判断【零钱支付 微信支付】  4.将订单编号返回

        // 1.做提交订单的校验
        // 1.1 基础数据的校验---spring做好了。@Validate,只有这个注解加上了，属性上的@NotEmpty注解才生效
        // 1.2 业务层的校验
        // a)验证订单信息的完整性
        // b)验证订单信息的时效性--主要保证订单尽可能小的被篡改的风险
        // c)重复提交的校验
        // d)非法请求的校验--就做了abcd4个校验方式
        // e)请求次数限制的校验
        // e)请求的ip做地域的校验
        // f)请求ip做黑白名单校验
        // g)在窗口期做请求速率的限制
        // .....

        // 1.业务层的校验
        // 1.1 校验请求的非法性。必须先请求了订单页接口才能请求提交订单接口。这里用查看是否有订单编号来做校验。
        String tradeNo = orderInfoVo.getTradeNo();
        if (StringUtils.isEmpty(tradeNo)) {
            throw new GuiguException(201, "非法请求");
        }
        // 1.2 校验订单信息的完整性以及时效性
        String orderInfoVoStr = JSONObject.toJSONString(orderInfoVo);
        Map signMap = JSONObject.parseObject(orderInfoVoStr, Map.class);
        signMap.put("payWay", ""); //其他订单中的这个值都是空的。这里再做校验时要还原上面为空的状态。
        SignHelper.checkSign(signMap);//这一个方法中做了完整性和时效性两个校验。
        // 先再做一次新签名---接着获取之前的签名---最后对比这两个签名是否相等，如果相等代表没有被篡改过 如果不相等 一定被篡改过
            //往下走代表信息没有被篡改过。

        // 1.3 订单重复提交的校验
        //  第一次提交订单之后，未看见后续的动作（1、网络原因 2.接口的并发高，处理慢）
        //  但是你误以为失败或者以为没点上 接着又点击一次。多次也是一样，都是重复了【这就导致了重复提交】
        //  解决办法：对于同一个请求来说，不管调用多少次，数据都要是一致的,产生的结果是符合预期的。
        //  接口幂等性保证：方案：各种锁机制（分布式锁 本地锁） MySQL的唯一索引+防重表+本地事务  Redis+Token(最多，用的这个)
        // 1(订单id)--->1001(订单编号)--->"买了个飞机"（订单名字）---100（订单的价格）
        // 2(订单id)--->1001(订单编号)--->"买了个飞机"（订单名字）---100（订单的价格）
/**
 * 为了解决多端重复提交的幂等性，确保不同同一账户下不同终端也是仅执行一次提交相同的订单，这里我第一次使用了lua脚本做了原子性校验的幂等性操作,
 * 即使后面多次提交了相同的订单，但是由于lua脚本的机制，只会执行一次，所以不会出现多次提交订单的情况。
 * 后面遇到了专辑表有id并且声音表也有id,并且可以回出现id相同的情况，为了防止id相同的redis中key的问题。为了限制这个问题。
 *      这里对保存到redis中的防止重复的key的内容进行了修改，不再是md5加密后id而是内容，内容一定不同，就可以避免id重复保存到redis中。
 * lua在单端重复提交时性能较好，但是在多端重复提交时，可能会出现多次提交相同订单的情况。即使一端删除，但是其他端还是可以重复提交生成相同的key。这样就炸了
 * 所以最后使用的是redis的increment方法进行幂等性操作。
 */
        Long userId = AuthContextHolder.getUserId();
        //用id可能会导致专辑和声音用相同的id，导致重复。所以这里直接使用的是内容，内容一定不同，就可以避免id重复保存到redis中。
        List<OrderDetailVo> productList = orderInfoVo.getOrderDetailVoList().stream().collect(Collectors.toList());
        String representativeToken = MD5.encrypt(new String(productList + "")); /*有代表性的token*/
        String orderRepeatSubmitKey = userId + ":" + representativeToken;
//        这个是课件上抄过来的脚本（判断和删除一定要写一个lua脚本。第一个订单redis中有才能去删。当任意时刻出现网络故障导致第一第二都进来，
//        然后在redis中进行判断时，又都有，那就只有一条重复提交的key进行删除。第二条删除不到key(第二和第一的key相同)，就用lua脚本断定，即使进来了，
//        但是删除没有成功也是失败。）
//        String luaScript = "if redis.call(\"exists\",KEYS[1])\n" + "then\n" + "    return redis.call(\"del\",KEYS[1])\n" + "else\n" + "    return 0\n" + "end";
//        Long execute = redisTemplate.execute(new DefaultRedisScript<Long>(luaScript, Long.class), Arrays.asList(orderRepeatSubmitKey));
//        if (execute == 0) {
//            throw new GuiguException(201, "订单重复提交");
//        }

        Long increment = redisTemplate.opsForValue().increment(orderRepeatSubmitKey);
        if (increment > 2) {
            throw new GuiguException(201, "订单重复提交");
        }


//         查询数据库---根据订单编号查询数据库  TODO(查询数据库)
        /**
         * 参数       含义              说明
         * 12  生成字符串的长度   表示生成的随机字符串长度为 12 个字符
         * true  是否包含字母    表示生成的字符串可以包含英文字母（a-z, A-Z）
         * true  是否包含数字     示生成的字符串可以包含数字（0-9）
         * 总结：
         * 该方法会生成一个 12 位、包含大小写字母和数字 的随机字符串，通常用于生成订单编号（orderNo），确保其唯一性和不可预测性。
         */
        // 2.生成订单编号
        String orderNo = RandomStringUtils.random(12, true, true);

        // 3 .保存订单信息(核心代码)
        // 3.判断支付方式
        // if  else if else...优化  优化方式1：使用swtich case. 优化2：适配器模式
        String payWay = orderInfoVo.getPayWay();
//        if ("1101".equals(payWay)) {
//            // 微信处理逻辑
//        } else if ("1102".equals(payWay)) {
//            // 支付宝处理逻辑
//        } else {
//            // 零钱支付方式
//        }
        /*
         @Autowired
        private List<PayWay> payWayService;
        //因为这里是使用的list集合把接口包起来了，在下面的适配器模式中就会for循环依次匹配具体是哪种支付方式
         */
        // 适配器方式
        for (PayWay way : payWayService) {
            if (way.isSupport(payWay)) {
                way.dealPayWay(orderInfoVo, userId, orderNo); //这里重要的是零钱支付的处理逻辑
            }
        }
        // 5.订单编号返回
        //只有零钱够支付，才可以提交订单

        Map<String, Object> map = new HashMap<>();
        map.put("orderNo", orderNo);
        redisTemplate.delete(orderRepeatSubmitKey);
        System.out.println("12e123123123");
        return map;

    }

    /**
     *
     * 当该注解应用在一个方法上时，Spring 会为这个方法开启一个新的事务。
     * 如果方法正常执行完成（没有抛出异常），事务会被提交，所有的数据库更改都会被保存。
     * 如果方法执行过程中抛出了指定的异常（这里是 Exception.class），事务会被回滚，所有的数据库更改都不会被保存。
     * rollbackFor = Exception.class 的作用：
     * 默认情况下，Spring 只会在遇到未检查异常（即继承自 RuntimeException 的异常）时回滚事务。
     * 通过设置 rollbackFor = Exception.class，Spring 会在遇到任何类型的异常（包括受检异常，如 IOException、SQLException 等）时都触发事务回滚。
     * 这样可以确保即使发生受检异常，事务也能正确地回滚，避免数据不一致的问题。
     * @param orderInfoVo
     * @param userId
     * @param orderNo
     * @return
     */

   // @GlobalTransactional(name="createOrder",rollbackFor=Exception.class)
   @Transactional(rollbackFor = Exception.class)
    public OrderInfo saveOrderInfo(OrderInfoVo orderInfoVo, Long userId, String orderNo) {
        OrderInfo orderInfo = null;

        try {
            // 1.保存订单基本信息
            orderInfo = saveOrderBasicInfo(orderInfoVo, userId, orderNo);

            // 2.保存订单详情【一定会有】

            saveOrderDetail(orderInfo.getId(), orderInfoVo); //流水表

            // 3.保存订单减免【声音不减免  专辑  vip减免】
            saveOrderDerate(orderInfo.getId(), orderInfoVo);

           // int i = 1 / 0;
        } catch (Exception e) {
            throw new GuiguException(400, "服务内部解析数据出现了异常");
        }

        return orderInfo;

    }

    @Override
    public void PaySuccess(Long userId, String orderNo) {

        // 1.修改订单的状态为已支付（1.1 判断订单状态 1.2 修改订单状态）
        int count = orderInfoMapper.updateOrderStatus(orderNo, userId);
        if (count == 0) {
            log.error("修改订单状态失败");
        } else {
            log.info("修改订单状态成功");
        }

        // 2.像tingshu_user库中的相关表中插入流水记录（user_paid_album user_paid_track user_vip_service）
        //流水插进去就行，没必要让他阻塞住，所以使用rabbitMQ
        UserPaidRecordVo userPaidRecordVo = prepareUserPaidRecord(orderNo, userId);
        rabbitService.sendMessage(MqConst.EXCHANGE_USER, MqConst.ROUTING_USER_PAY_RECORD, JSONObject.toJSONString(userPaidRecordVo));

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("orderNo", orderNo);
        jsonObject.put("userId", userId);
        // 3.修改titngshu_album库中的album_stat的购买量[MySQL]
        rabbitService.sendMessage(MqConst.EXCHANGE_ALBUM, MqConst.ROUTING_ALBUM_STAT_UPDATE, jsonObject.toJSONString());

        // 4.修改album_info索引库的的专辑的购买量[ES]
        rabbitService.sendMessage(MqConst.EXCHANGE_ES_ALBUM_STAT, MqConst.ROUTING_ES_ALBUM_STAT, jsonObject.toJSONString());


    }

    @Override
    public OrderInfo getOrderInfo(String orderNo, Long userId) {


        // 1.根据订单编号查询订单基本信息
        LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(OrderInfo::getOrderNo, orderNo);
        wrapper.eq(OrderInfo::getUserId, userId);
        OrderInfo orderInfo = orderInfoMapper.selectOne(wrapper);

        // 2.封住订单的详情数据
        List<OrderDetail> orderDetailList = orderDetailMapper.selectList(new LambdaQueryWrapper<OrderDetail>().eq(OrderDetail::getOrderId, orderInfo.getId()));
        orderInfo.setOrderDetailList(orderDetailList);
        // 3.封住订单的减免数据
        List<OrderDerate> orderDerateList = orderDerateMapper.selectList(new LambdaQueryWrapper<OrderDerate>().eq(OrderDerate::getOrderId, orderInfo.getId()));
        orderInfo.setOrderDerateList(orderDerateList);
        // 4.封装订单的状态名字（前端）
        String orderInfoStatus = orderInfo.getOrderStatus();
        String orderInfoStatusName = getOrderInfoStatusName(orderInfoStatus);
        orderInfo.setOrderStatusName(orderInfoStatusName);

        // 5.封装订单的支付名字（前端）
        String orderInfoPayWay = orderInfo.getPayWay();
        String orderInfoPayWayName = getOrderInfoPayWayName(orderInfoPayWay);
        orderInfo.setPayWayName(orderInfoPayWayName);
        return orderInfo;
    }

    @Override
    public IPage<OrderInfo> findUserPage(IPage<OrderInfo> orderInfoPage, Long userId) {


        // 1.查询我的订单
        LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(OrderInfo::getUserId, userId);
        wrapper.orderByDesc(OrderInfo::getUpdateTime);
        wrapper.eq(OrderInfo::getIsDeleted, 0);
        IPage<OrderInfo> orderInfoIPage = this.page(orderInfoPage, wrapper);

        orderInfoIPage.getRecords().stream().forEach(orderInfo -> {

            String orderInfoStatusName = getOrderInfoStatusName(orderInfo.getOrderStatus());
            orderInfo.setOrderStatusName(orderInfoStatusName); // 订单状态名字
            String orderInfoPayWayName = getOrderInfoPayWayName(orderInfo.getPayWay());
            orderInfo.setPayWayName(orderInfoPayWayName); // 订单支付方式名字

            LambdaQueryWrapper<OrderDetail> wrapper1 = new LambdaQueryWrapper<>();
            wrapper1.eq(OrderDetail::getOrderId, orderInfo.getId());
            List<OrderDetail> orderDetailList = orderDetailMapper.selectList(wrapper1);
            orderInfo.setOrderDetailList(orderDetailList); // 订单详情数据

            LambdaQueryWrapper<OrderDerate> wrapper2 = new LambdaQueryWrapper<>();
            wrapper2.eq(OrderDerate::getOrderId, orderInfo.getId());
            List<OrderDerate> orderDerateList = orderDerateMapper.selectList(wrapper2);
            orderInfo.setOrderDerateList(orderDerateList);// 订单减免数据

        });

        // 2.返回修改后的orderInfoIPage对象的属性

        return orderInfoIPage;

    }

    private String getOrderInfoPayWayName(String orderInfoPayWay) {

        String orderInfoPayWayName = "";

        switch (orderInfoPayWay) {
            case "1101": //微信支付
                orderInfoPayWayName = "微信支付";
                break;
            case "1102": // 支付宝支付
                orderInfoPayWayName = "支付宝支付";
                break;
            case "1103": // 账户余额支付
                orderInfoPayWayName = "账户余额支付";
        }
        return orderInfoPayWayName;

    }

    private String getOrderInfoStatusName(String orderInfoStatus) {

        String orderInfoStatusName = "";

        switch (orderInfoStatus) {
            case "0901": // 未支付
                orderInfoStatusName = "未支付";
                break;
            case "0902": // 已支付
                orderInfoStatusName = "已支付";
                break;
            case "0903": // 已关闭
                orderInfoStatusName = "已关闭";
        }
        return orderInfoStatusName;
    }


    private UserPaidRecordVo prepareUserPaidRecord(String orderNo, Long userId) {


        UserPaidRecordVo userPaidRecordVo = new UserPaidRecordVo();
        userPaidRecordVo.setOrderNo(orderNo);
        userPaidRecordVo.setUserId(userId);

        OrderInfo orderInfo = getOrderInfo(orderNo, userId);
        userPaidRecordVo.setItemType(orderInfo.getItemType()); // 付款项类型
        List<Long> itemIdList = orderInfo.getOrderDetailList().stream().map(OrderDetail::getItemId).collect(Collectors.toList());
        userPaidRecordVo.setItemIdList(itemIdList);

        return userPaidRecordVo;

    }

    private void saveOrderDerate(Long orderId, OrderInfoVo orderInfoVo) {
        orderInfoVo.getOrderDerateVoList().forEach(orderDerateVo -> {
            OrderDerate orderDerate = new OrderDerate();
            orderDerate.setOrderId(orderId);
            orderDerate.setDerateType(orderDerateVo.getDerateType());
            orderDerate.setDerateAmount(orderDerateVo.getDerateAmount());
            orderDerate.setRemarks("有商品减免");
            orderDerateMapper.insert(orderDerate);
        });
    }

    private void saveOrderDetail(Long orderId, OrderInfoVo orderInfoVo) {
        orderInfoVo.getOrderDetailVoList().stream().forEach(orderDetailVo -> {
            OrderDetail orderDetail = new OrderDetail();
            orderDetail.setOrderId(orderId);
            orderDetail.setItemId(orderDetailVo.getItemId());
            orderDetail.setItemName(orderDetailVo.getItemName());
            orderDetail.setItemUrl(orderDetailVo.getItemUrl());
            orderDetail.setItemPrice(orderDetailVo.getItemPrice());
            orderDetailMapper.insert(orderDetail);
        });

    }

    private OrderInfo saveOrderBasicInfo(OrderInfoVo orderInfoVo, Long userId, String orderNo) {

        OrderInfo orderInfo = new OrderInfo();
        orderInfo.setUserId(userId);
        orderInfo.setOrderTitle(orderInfoVo.getOrderDetailVoList().get(0).getItemName());
        orderInfo.setOrderNo(orderNo);
        orderInfo.setOrderStatus(SystemConstant.ORDER_STATUS_UNPAID);
        orderInfo.setOriginalAmount(orderInfoVo.getOriginalAmount());  // 订单的原始金额
        orderInfo.setDerateAmount(orderInfoVo.getDerateAmount()); // 订单的减免金额
        orderInfo.setOrderAmount(orderInfoVo.getOrderAmount()); // 订单的实际金额
        orderInfo.setItemType(orderInfoVo.getItemType());
        orderInfo.setPayWay(orderInfoVo.getPayWay());


        orderInfoMapper.insert(orderInfo);

        return orderInfo;
    }

    /**
     * 处理付款项类型是vip套餐的结算页
     *
     * @param tradeVo
     * @param userId
     * @return
     */
    private OrderInfoVo dealItemTypeVip(TradeVo tradeVo, Long userId) {

        // 1.构建结算页对象
        OrderInfoVo orderInfoVo = new OrderInfoVo();

        // 2.1.校验 某一个套餐是否支付过（不用做）。随便你购买，无限延长vip时间。

        // 2.2 校验 某一个套餐是否下单未支付（要做） 如果用户下单了某一个套餐 但是还未支付 接着该用户在次点击购买该套餐，
        // 提示当前用户该套餐已下单，准备支付 请勿再次提交。
/**
 * 判断（专辑或套餐）是不是下单未支付
 */
        Long count = orderInfoMapper.getItemTypeAlbumAndVipIsPadding(userId, tradeVo.getItemType(), tradeVo.getItemId());
        if (count > 0) {
            log.info("当前购买的某一个套餐下单未支付，请勿重复下单!");
            List<Long> exitItemIds = new ArrayList<>();
            exitItemIds.add(tradeVo.getItemId());
            orderInfoVo.setExitItemIdList(exitItemIds);
            orderInfoVo.setOrderDetailVoList(new ArrayList<>());
            return orderInfoVo;
        }
        String tradeNo = RandomStringUtils.random(10, false, true);
        orderInfoVo.setTradeNo(tradeNo);
        orderInfoVo.setPayWay("");
        orderInfoVo.setItemType(tradeVo.getItemType());

        Result<VipServiceConfig> vipServiceConfigResult = userInfoFeignClient.getVipConfigById(tradeVo.getItemId());
        VipServiceConfig serviceConfigResultData = vipServiceConfigResult.getData();
        Assert.notNull(serviceConfigResultData, "远程查询用户微服务获取套餐信息失败");
        BigDecimal originalAmount = serviceConfigResultData.getPrice(); // 获取套餐原始金额
        BigDecimal discountPrice = serviceConfigResultData.getDiscountPrice();// 获取套餐优惠金额
        orderInfoVo.setOriginalAmount(originalAmount.setScale(2, BigDecimal.ROUND_HALF_UP)); // 原始金额
        orderInfoVo.setDerateAmount(originalAmount.subtract(discountPrice).setScale(2, BigDecimal.ROUND_HALF_UP)); // 优惠掉多少金额
        orderInfoVo.setOrderAmount(discountPrice.setScale(2, BigDecimal.ROUND_HALF_UP)); // 实际金额


        // 构建结算页详情
        List<OrderDetailVo> orderDetailVos = new ArrayList<>();
        OrderDetailVo orderDetailVo = new OrderDetailVo();
        orderDetailVo.setItemId(tradeVo.getItemId());
        orderDetailVo.setItemName(serviceConfigResultData.getName());
        orderDetailVo.setItemUrl(serviceConfigResultData.getImageUrl());
        orderDetailVo.setItemPrice(originalAmount.setScale(2, BigDecimal.ROUND_HALF_UP));// 原始金额
        orderDetailVos.add(orderDetailVo);
        orderInfoVo.setOrderDetailVoList(Lists.newArrayList(orderDetailVos));

        // 构建结算页减免
        List<OrderDerateVo> orderDerateVoList = new ArrayList<>();
        OrderDerateVo orderDerateVo = new OrderDerateVo();
        orderDerateVo.setDerateType("1406-VIP服务折扣");
        orderDerateVo.setDerateAmount(discountPrice.setScale(2, BigDecimal.ROUND_HALF_UP));  // 优惠后
        orderDerateVo.setRemarks("套餐参与了优惠");
        orderDerateVoList.add(orderDerateVo);
        orderInfoVo.setOrderDerateVoList(Lists.newArrayList(orderDerateVoList));

        orderInfoVo.setTimestamp(System.currentTimeMillis());

        return orderInfoVo;
    }


    /**
     * 处理付款项类型是声音的结算页
     *
     * @param tradeVo
     * @param userId
     * @return
     */
    private OrderInfoVo dealItemTypeTrack(TradeVo tradeVo, Long userId) {

        OrderInfoVo orderInfoVo = new OrderInfoVo();

        // 0.该声音是否支付过（一定是没有支付过）在分集展示声音的时候 已经将买过的声音过滤掉了（幂等性不用在做了）

//        该声音是否下单但是未支付（要做幂等性校验）
        // 1.查询要买那些声音
        Long trackId = tradeVo.getItemId();  // 当前声音id
        Integer trackCount = tradeVo.getTrackCount();//  当前声音的全部数量
//将要买的声音查询出来
        // 1.1 查询声音对象
        Result<TrackInfo> trackInfoResult = albumInfoFeignClient.getTrackInfoByTrackId(trackId);
        TrackInfo trackInfoResultData = trackInfoResult.getData();
        if (trackInfoResultData == null) {
            throw new GuiguException(201, "该声音不存在");
        }

        // 1.2 查询专辑对象
        //根据声音id 查询专辑对象
        Long albumId = trackInfoResultData.getAlbumId();
        Result<AlbumInfo> albumInfoAndAttrValue = albumInfoFeignClient.getAlbumInfoAndAttrValue(albumId);
        AlbumInfo albumInfo = albumInfoAndAttrValue.getData();
        if (albumInfo == null) {
            throw new GuiguException(201, "该声音对应的专辑不存在");
        }
        // 1.3 查询指定集数的声音列表，根据当前声音id查询其余未下单的声音
        //查询当前声音后多少集声音列表
        Result<List<TrackInfo>> trackListByCurrentTrackId = albumInfoFeignClient.getTrackListByCurrentTrackId(userId, trackId, trackCount);
        List<TrackInfo> trackIdData = trackListByCurrentTrackId.getData();
        if (CollectionUtils.isEmpty(trackIdData)) {
            throw new GuiguException(201, "远程查询专辑微服务获取声音后指定集数的声音失败");//todo 老师这里写错字了
        }


        // 2.该声音是否下单但是未支付（幂等性要做），根据用户id、付款项类型1002声音、订单状态0901未支付 查询没有支付的声音提交的订单
        //submitAndUnPaidTrackIds 它表示：当前用户已经下单、但尚未支付的声音 ID 列表。
        List<Long> submitAndUnPaidTrackIds = orderInfoMapper.getItemTypeTrackIsPadding(userId, tradeVo.getItemType());
        //筛选出其余声音中已经被用户下单但尚未支付的声音
        List<TrackInfo> trackInfoList = trackIdData.stream().filter(trackInfo ->
                submitAndUnPaidTrackIds.contains(trackInfo.getId())).collect(Collectors.toList()); // 过滤后获得未下单的声音
        if (!CollectionUtils.isEmpty(trackInfoList)) {
            //当前要购买的声音都处于未支付状态
            List<Long> exitTrackIds = new ArrayList<>();
            for (TrackInfo trackInfo : trackInfoList) {
                exitTrackIds.add(trackInfo.getId());//把未下单的声音id加入到集合中返回给前端
            }
            orderInfoVo.setExitItemIdList(exitTrackIds);
            orderInfoVo.setOrderDetailVoList(new ArrayList<>());
            log.error("该声音已下单，请勿重复购买！");
            return orderInfoVo;
        } else {
            //当前要购买的声音都没有处于下单中，都未支付中
            //随机生成订单编号只有纯数字没有字母
            String tradeNo = RandomStringUtils.random(10, false, true);
            orderInfoVo.setTradeNo(tradeNo);//订单编号
            orderInfoVo.setPayWay("");
            orderInfoVo.setItemType(tradeVo.getItemType()); //付款项类型: 1001-专辑 1002-声音 1003-vip会员 ，这里是声音1002
            BigDecimal price = albumInfo.getPrice(); // 单集声音的价格
            //判断买的是本集的还是后多少集的
            BigDecimal originalAmount = trackCount == 0 ? price : price.multiply(new BigDecimal(trackCount));
            orderInfoVo.setOriginalAmount(originalAmount.setScale(2, BigDecimal.ROUND_HALF_UP));  // 原始金额
            orderInfoVo.setDerateAmount(new BigDecimal("0.00")); // 优惠金额  (声音没有优惠abbum_info表中写明了声音不支持折扣 )
            orderInfoVo.setOrderAmount(originalAmount.setScale(2, BigDecimal.ROUND_HALF_UP)); // 实际金额

            List<OrderDetailVo> orderDetailVos = new ArrayList<>();
            // 封装结算页详情数据
            for (TrackInfo trackInfo : trackIdData) { //这里要遍历的是其他要买的声音
                OrderDetailVo orderDetailVo = new OrderDetailVo();// 封装订单中商品为声音的各个信息
                orderDetailVo.setItemId(trackInfo.getId());
                orderDetailVo.setItemName(trackInfo.getTrackTitle());
                orderDetailVo.setItemUrl(trackInfo.getCoverUrl());
                orderDetailVo.setItemPrice(price.setScale(2, BigDecimal.ROUND_HALF_UP));// 每一集声音价格
                orderDetailVos.add(orderDetailVo);
            }
            orderInfoVo.setOrderDetailVoList(Lists.newArrayList(orderDetailVos));

            // 封装结算减免数据
            orderInfoVo.setOrderDerateVoList(Lists.newArrayList());
        }
        // 3.返回结算页对象
        return orderInfoVo;
    }

    /**
     * 处理付款项类型是专辑的结算页
     *
     * @param tradeVo
     * @param userId
     * @return
     */
    private OrderInfoVo dealItemTypeAlbum(TradeVo tradeVo, Long userId) {
/**
 #`order_info` `order_detail`
 # 查询 当前用户是正在支付某个专辑
 SELECT
 COUNT(*)
 FROM order_info
 INNER JOIN order_detail
 ON order_info.`id`=order_detail.`order_id`
 #10号用户购买的10号专辑，类型1001专辑，0901是下单未支付
 WHERE order_info.`user_id`=10 AND order_info.`item_type`=1001 AND order_info.`order_status`='0901' AND order_detail.item_id=10;
 */
        // 1.创建结算页对象
        OrderInfoVo orderInfoVo = new OrderInfoVo();//创建对象之前先进行幂等性校验，能通过校验才能赋值，不能通过就不赋值
        Long itemId = tradeVo.getItemId();
        //虚拟商品买过一次就不需要你再买了。
//  这里有两次幂等性校验，第一次openFeign远程同步查询数据库，用户是否购买过该专辑，第二次查询是否下单未支付
        // 1.1 查询要购买的该专辑是否已经支付过（幂等性校验）。查询用户名下的数据中已经支付的专辑表中是否有该专辑
        Result<Boolean> userPaidAlbum = userInfoFeignClient.getUserPaidAlbum(userId, itemId);
        Boolean aBoolean = userPaidAlbum.getData();
        if (aBoolean == null) {
            throw new GuiguException(201, "远程调用用户微服务获取用户是否购买过专辑失败");
        }
        if (aBoolean) {
            log.info("该专辑已经购买过，请勿重复购买!");
            List<Long> exitItemIds = new ArrayList<>();
            exitItemIds.add(itemId);
            orderInfoVo.setExitItemIdList(exitItemIds);
            orderInfoVo.setOrderDetailVoList(new ArrayList<>());
            return orderInfoVo;
        }

        // 1.2 查询要购买的该专辑是下单但是未支付（幂等性校验）
        //查询订单信息表和订单详情表，看当前用户是否下单但是未支付
        Long count = orderInfoMapper.getItemTypeAlbumAndVipIsPadding(userId, tradeVo.getItemType(), itemId);
        if (count > 0) {
            log.info("当前购买的专辑下单未支付，请勿重复下单!");
            List<Long> exitItemIds = new ArrayList<>();
            exitItemIds.add(itemId); //说明已经有这个专辑了，这个专辑还未支付，添加这个专辑到已购买的专辑列表中
            orderInfoVo.setExitItemIdList(exitItemIds);
            orderInfoVo.setOrderDetailVoList(new ArrayList<>());
            return orderInfoVo;//然后直接返回出去给前端
        }
//只有上面两种情况中的（1，数据库中没有数据-->没有买过，2没有再买。）一种就不会再买了，就不会再生成结算页信息。
// 上面两种都没有查到数据才可以生成结算页信息。

        // 2.给结算页对象赋值
        String tradeNo = RandomStringUtils.random(10, false, true);
        orderInfoVo.setTradeNo(tradeNo); // 防止订单重复提交--->本质：跟踪订单。不跟踪id的原因是，
        // 还没有进行数据库操作，数据库中没有记录。所以不能使用id
        orderInfoVo.setPayWay("");// 支付方式不知道
        orderInfoVo.setItemType(tradeVo.getItemType()); // 付款项类型

        // 2.1 远程查询专辑对象
        Result<AlbumInfo> albumInfoAndAttrValue = albumInfoFeignClient.getAlbumInfoAndAttrValue(itemId);
        AlbumInfo albumInfoData = albumInfoAndAttrValue.getData();
        if (albumInfoData == null) {
            throw new GuiguException(201, "远程查询专辑信息失败");
        }

        // 2.2 远程查询用户信息看是不是vip
        Result<UserInfoVo> userInfo = userInfoFeignClient.getUserInfo(userId);
        UserInfoVo userInfoData = userInfo.getData();
        Assert.notNull(userInfoData, "远程查询用户微服务获取用户信息失败");
        Integer isVip = userInfoData.getIsVip();

        // 2.3 获取折扣粒度
        BigDecimal disCount = new BigDecimal("0.00");// 折扣粒度
        // vip
        if ((isVip + "").equals("1")) {
            disCount = albumInfoData.getVipDiscount();
        } else {
            disCount = albumInfoData.getDiscount();
        }
        if (disCount.intValue() == -1) {// 不打折
            disCount = new BigDecimal("10.00");
        }

        //. 2.4 原始金额
        BigDecimal originalPrice = albumInfoData.getPrice();// 专辑的价格 未优惠的
        orderInfoVo.setOriginalAmount(originalPrice.setScale(2, BigDecimal.ROUND_HALF_UP));  // 专辑的原始金额（未参与打折优惠的）
//        这行代码表示将专辑原始价格保留两位小数，并使用四舍五入的方式处理多余的位数，确保金额格式符合常见的货币显示规范（如：元、美元等）。
//这里的setScale 是 BigDecimal 类中的一个方法，用于设置数值的精度（小数位数）和舍入方式。它在金融计算、订单金额处理等需要精确控制小数精度的场景中非常常用。
        /**
         * newScale：要保留的小数位数。例如，2 表示保留两位小数。
         * roundingMode：舍入模式，决定如何对多余的小数位进行舍入。常用的有：
         * BigDecimal.ROUND_HALF_UP：四舍五入（最常见）
         * BigDecimal.ROUND_DOWN：直接截断，不进位
         * BigDecimal.ROUND_UP：进位处理
         * BigDecimal.ROUND_HALF_DOWN：五舍六入
         */
        // 100 7  30=（100-7*10）;   // 原价 30  打折：7 （10）  实际：21 30  优惠30-21=9
        // 2.5 实际金额 = 原始金额 * 折扣率/10
        BigDecimal orderAmount = originalPrice.multiply(disCount).divide(new BigDecimal(10)).setScale(2, BigDecimal.ROUND_HALF_UP);
//根据折扣金额和原始金额，得出实际金额。然后再根据实际金额再算出减免金额。
        // 2.6 优惠金额 = 原始金额 - 实际金额 30*7/10 = 21.00
        BigDecimal derateAmount = originalPrice.subtract(orderAmount).setScale(2, BigDecimal.ROUND_HALF_UP);
        orderInfoVo.setDerateAmount(derateAmount);// 专辑的减免金额：【原件--折扣价】 原价：100  折扣后的金额 70  优惠的价格：30（100-70=30）
        orderInfoVo.setOrderAmount(orderAmount);// 实际金额 = 实际金额 = 原始金额 * 折扣率/10

        // 2.7 构建订单详情明细，到底买的什么东西
        List<OrderDetailVo> orderDetailVos = new ArrayList<>();
        OrderDetailVo orderDetailVo = new OrderDetailVo();
        orderDetailVo.setItemId(itemId); // 专辑id
        orderDetailVo.setItemName(albumInfoData.getAlbumTitle()); // 专辑的名字
        orderDetailVo.setItemUrl(albumInfoData.getCoverUrl()); // 专辑的封面
        orderDetailVo.setItemPrice(originalPrice); // 专辑的原始金额
        orderDetailVos.add(orderDetailVo);
        orderInfoVo.setOrderDetailVoList(Lists.newArrayList(orderDetailVos));  // 订单明细：到底买的是什么东西

        // 2.8 构建订单减免明细 减免的东西是谁
        List<OrderDerateVo> orderDerateVoList = new ArrayList<>();
        OrderDerateVo orderDerateVo = new OrderDerateVo();
        orderDerateVo.setDerateType("1405-专辑折扣");
        orderDerateVo.setDerateAmount(derateAmount);
        orderDerateVo.setRemarks("专辑打折了");
        orderDerateVoList.add(orderDerateVo);
        orderInfoVo.setOrderDerateVoList(Lists.newArrayList(orderDerateVoList)); // 减免明细：到底减免的东西是谁
        orderInfoVo.setTimestamp(System.currentTimeMillis()); // 订单结算页数据的时间戳（为了做订单完整性校验）
        /**
         * 订单结算页数据的时间戳（为了做订单完整性校验）
         * 订单结算页数据的签名（为了做订单完整性校验）
         */

        // 3.将结算页对象返回
        return orderInfoVo;
    }
}
